/*
 * @Description:
 * @Author: Evan Zhang. evan.holmes@foxmail.com
 * @Date: 2023-01-09 11:11:59
 * @LastEditTime: 2024-06-03 16:16:10
 * @LastEditors: Evan Zhang. evan.holmes@foxmail.com
 * @Reference:
 */

function getParentPercent(element) {
	if (!element) throw new TypeError('Need HTMLElement')
	let total_scale = 1,
		ret = element.parentNode
	while (ret.parentNode) {
		// to look up scale percent
		let scale = 1,
			transform = (getComputedStyle(ret) || {}).transform
		if (transform.includes('matrix')) {
			const matrix = transform.split('matrix(')[1].split(')')[0].split(',')
			if (matrix[0] === matrix[3]) scale = matrix[0] * 1
			else scale = matrix[0] * 1 // TODO poop code
		}
		total_scale = total_scale * scale
		ret = ret.parentNode
	}
	return total_scale
}

/**
 * @description:
 * @return {select:function} 选项
 * @param {HTMLElement} el
 */
export default function eDrag() {
	/**
	 * @description: 选择一个元素
	 * @return {*}
	 * @param {HTMLElement} el
	 */
	function select(el = document.body) {
		/**
		 * @description: 缩放元素
		 * @return {*}
		 */
		function zoom() {
			let _delta = 0.01
			let _reverse_direction = false
			let _match_scale_reg = /scal(eX|eY|e)\([0-9].+\)/
			let scale = 1
			let _change_scale_callback = function () {}

			if (!el) throw new Error("The 'El' is not defined! can't execute zoom")

			/**
			 * @description: 设置当缩放改变时的回调函数
			 * @return {*}
			 * @param {*} callback
			 */
			function change(callback) {
				if (!typeof callback === 'function')
					throw new TypeError(`need function but got the '${typeof callback}'`)
				_change_scale_callback = callback
			}

			/**
			 * @description: 滚轮事件绑定
			 * @return {*}
			 * @param {*} event
			 */
			function onWheel(event) {
				let isUp = event.wheelDelta || event.detail
				let d = _delta * (isUp < 0 ? 1 : -1) // compute delta
				if (_reverse_direction) d = d * -1 // reverse the direction.
				scale = scale + d
				_change_scale_callback && _change_scale_callback(scale)
				let _scale_str = `scale(${scale})`

				if (!el.style.transform) el.style.transform = _scale_str
				else {
					/**
					 * their handler case's for if the 'transform' exist, if the 'transform' attribute
					 * already has the scale value, then replace to new value, else then add scale value into it.
					 */
					if (!_match_scale_reg.test(el.style.transform))
						el.style.transform += ' ' + _scale_str
					else {
						el.style.transform = el.style.transform.replace(
							_match_scale_reg,
							_scale_str
						)
					}
				}
			}

			/**
			 * @description: 缩放增加/减少量
			 * @return {delta:function,reverse:function}
			 * @param {Number} val
			 */
			function delta(val = 0.01) {
				_delta = val
				return {
					delta,
					change,
					reverse
				}
			}
			/**
			 * @description: 反转放大/缩小
			 * @return {delta:function,reverse:function}
			 */
			function reverse() {
				_reverse_direction = !_reverse_direction
				return {
					delta,
					change,
					reverse
				}
			}

			let addEvent = el.addEventListener || el.attachEvent
			addEvent('wheel', onWheel, false)
			return {
				delta,
				change,
				reverse
			}
		}

		function drag() {
			if (!el) throw new Error("The 'El' is not defined! can't execute drag")

			let eventsBus = Object.create(null)

			function on(eventName, callbackFunction) {
				if (typeof callbackFunction !== 'function')
					throw new TypeError(
						`Type error! need a function but got a ${typeof callbackFunction}`
					)
				let res = eventsBus[eventName] || [] // get function pool
				res.push(callbackFunction)
				if (Array.isArray(eventsBus[eventName]))
					return { on, off } // already exist function pool, success add it.
				else eventsBus[eventName] = res // create a new function pool and mount it.
				return { on, off }
			}

			function off(eventName, callbackFunction) {
				if (typeof callbackFunction !== 'function') return
				let res = eventsBus[eventName]
				if (!Array.isArray(res)) return // not need unmount.
				else {
					// don't change array size
					let idx = res.indexOf(callbackFunction)
					if (idx === -1) return { on, off }
					res[idx] = null
				}
				return { on, off }
			}
			function triggerEvent(eventName, data) {
				let res = eventsBus[eventName]
				if (!Array.isArray(res)) return // not need trigger callback.
				// trigger all function.
				res
					.filter(i => typeof i === 'function')
					.forEach(callback => {
						try {
							// increase robustness
							callback(data)
						} catch (error) {
							console.error(error)
						}
					})
			}

			/**
			 * mouse Object to record the :
			 *  1, start position
			 * 	2, is moving state
			 *  3, current position
			 *  4, element received scale percent
			 *
			 * and can triggered event's includes 'start','change','done'
			 */

			let mouse = {
				start: { x: 0, y: 0, dx: 0, dy: 0, vdx: 0, vdy: 0 },
				position: { x: 0, y: 0, dx: 0, dy: 0, vdx: 0, vdy: 0 },
				moving: false,
				percent: 1
			}
			const MOUSE_MOVE_START = 'start'
			const MOUSE_MOVE_CHANGE = 'change'
			const MOUSE_MOVE_DONE = 'done'

			function mousedown(e) {
				const x = (mouse.start.x = e.clientX)
				const y = (mouse.start.y = e.clientY)
				// get elements percent of increment received
				const p = (mouse.percent = getParentPercent(el))
				// inverse ratio zoom, zoom in then number will be decrease，
				// zoom out the number will be increase
				mouse.start.virtual_x = (1 / p) * x
				mouse.start.virtual_y = (1 / p) * y
				// unlock moving states.
				mouse.moving = true
				let d = JSON.parse(JSON.stringify(mouse))
				d.element = e.srcElement
				triggerEvent(MOUSE_MOVE_START, d)
			}

			function mousemove(e) {
				if (!mouse.moving) return // is moving ?

				const x = (mouse.position.x = e.clientX)
				const y = (mouse.position.y = e.clientY)
				const p = (mouse.percent = getParentPercent(el))

				const virtual_x = (mouse.position.virtual_x = (1 / p) * x)
				const virtual_y = (mouse.position.virtual_y = (1 / p) * y)

				mouse.position.dx = x - mouse.start.x // absolute pixel
				mouse.position.dy = y - mouse.start.y // absolute pixel
				mouse.position.vdx = virtual_x - mouse.start.virtual_x // relative pixel
				mouse.position.vdy = virtual_y - mouse.start.virtual_y // relative pixel

				let d = JSON.parse(JSON.stringify(mouse))
				d.element = e.srcElement
				triggerEvent(MOUSE_MOVE_CHANGE, d)
			}
			function resetMouse(e) {
				// lock mouse move state update.
				mouse.moving = false
				let d = JSON.parse(JSON.stringify(mouse))
				d.element = e.srcElement
				triggerEvent(MOUSE_MOVE_DONE, d)
				mouse = {
					start: { x: 0, y: 0, dx: 0, dy: 0, vdx: 0, vdy: 0 },
					position: { x: 0, y: 0, dx: 0, dy: 0, vdx: 0, vdy: 0 },
					moving: false,
					percent: 1
				} // reset mouse state
			}

			// sign to event listener.
			el.addEventListener('mousedown', mousedown)
			el.addEventListener('mousemove', mousemove)
			el.addEventListener('mouseleave', resetMouse)
			el.addEventListener('mouseup', resetMouse)

			return {
				on,
				off
			}
		}

		return {
			zoom,
			drag
		}
	}
	return {
		select
	}

	return {
		select
	}
}
